﻿#region File Description
/* Screen.cs
 * 
 * Author: fbrookie
 * Date: Thur 12/15/2011
 * Revision 2
 * 
 * A Screen object, the basic object for updating, handling input and displaying graphics.
 * Credit to Microsoft XNA Samples for basic code. Licensed under Microsoft Permissive License
 * -----------------------------------------------------------
 * GameScreen.cs (Adapted to ('Screen')
 *
 * Microsoft XNA Community Game Platform
 * Copyright (C) Microsoft Corporation. All rights reserved.
 * ------------------------------------------------------------
 */
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using SmlEngine.UI.Managers;
using SmlEngine.UI.Input;

namespace SmlEngine.UI.Screens
{
    /// <summary>
    /// A simple drawable component. This is managed by a ScreenManager and 
    /// should be the most basic component able to be drawn to the screen.
    /// Multiple screens can be layered to produce complex effects.
    /// </summary>
    public abstract class Screen : IPreloadAssets
    {
        #region Fields

        private bool anotherScreenHasFocus;
        private List<IScene> scenes;

        #endregion

        #region Properties

        internal bool ViewportChanged { get; private set; }

        public ScreenManager ScreenManager { get; internal set; }

        public bool IsPopup { get; protected set; }
        public bool IsExiting { get; protected internal set; }
        public bool IsActive
        {
            get
            {
                return !anotherScreenHasFocus &&
                       (ScreenState == ScreenState.TransitionOn ||
                        ScreenState == ScreenState.Active);
            }
        }

        public abstract IEnumerable<string> PreloadedAssets { get; }

        public IList<IScene> Scenes
        {
            get { return scenes; }
        }

        #region Transitioning

        public TimeSpan TransitionOnTime { get; protected set; }
        public TimeSpan TransitionOffTime { get; protected set; }

        public float TransitionPosition { get; protected set; }
        public float TransitionAlpha
        {
            get { return 1f - TransitionPosition; }
        }

        public ScreenState ScreenState { get; protected set; }

        #endregion

        #endregion

        #region Methods

        #region Constructors

        protected Screen()
        {
            IsPopup = false;

            TransitionOnTime = TimeSpan.Zero;
            TransitionOffTime = TimeSpan.Zero;
            TransitionPosition = 0;
            ScreenState = ScreenState.TransitionOn;

            scenes = new List<IScene>();
        }

        #endregion

        #region Initialization and Diposal

        /// <summary>
        /// Initializes ScreenManager-controlled resources.
        /// </summary>
        public virtual void Initialize() { }

        /// <summary>
        /// Load graphics content for the screen.
        /// </summary>
        public virtual void LoadContent(ContentManager content) { }

        /// <summary>
        /// Unload content for the screen.
        /// </summary>
        public virtual void UnloadContent() { }

        #endregion

        #region Update

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="hasFocus"></param>
        /// <param name="isCoveredByAnotherScreen"></param>
        public virtual void Update(GameTime gameTime, bool hasFocus, bool isCoveredByAnotherScreen)
        {
            anotherScreenHasFocus = !hasFocus;

            if (IsExiting)
            {
                //If the screen is going away permanently, transition off.
                ScreenState = ScreenState.TransitionOff;

                if (!UpdateTransition(gameTime, TransitionState.Off, TransitionOffTime))
                {
                    // When the transition finishes, remove the screen.
                    ScreenManager.RemoveScreen(this);
                }
            }
            else if (isCoveredByAnotherScreen)
            {
                // If the screen is covered by another, it should transition off.
                if (UpdateTransition(gameTime, TransitionState.Off, TransitionOffTime))
                {
                    // Still busy transitioning.
                    ScreenState = ScreenState.TransitionOff;
                }
                else
                {
                    // Transition finished!
                    ScreenState = ScreenState.Hidden;
                }
            }
            else
            {
                // Otherwise the screen should transition on and become active.
                if (UpdateTransition(gameTime, TransitionState.On, TransitionOffTime))
                {
                    // Still busy transitioning.
                    ScreenState = ScreenState.TransitionOn;
                }
                else
                {
                    // Transition finished!
                    ScreenState = ScreenState.Active;

                    UpdateScreens(gameTime);
                }
            }
        }
        
        /// <summary>
        /// Updates the scren transition.
        /// </summary>
        /// <param name="gameTime">The current GameTime, providing elapsed timing values.</param>
        /// <param name="state">
        /// The direction to update the transition.
        /// </param>
        /// <param name="time"></param>
        /// <returns></returns>
        protected bool UpdateTransition(GameTime gameTime, TransitionState state, TimeSpan time)
        {
            int direction = (int)state;

            // How much we should advance the transition by.
            float transitionDelta;

            if (time == TimeSpan.Zero)
                transitionDelta = 1;
            else
                transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds /
                                          time.TotalMilliseconds);

            // Update the Transition position.
            TransitionPosition += transitionDelta * direction;

            // If we have reached the end of the transition, return false.
            if (((direction < 0) && (TransitionPosition <= 0)) ||
                ((direction > 0) && (TransitionPosition >= 1)))
            {
                TransitionPosition = MathHelper.Clamp(TransitionPosition, 0, 1);
                return false;
            }

            // Otherwise we are still busy transitioning.
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gameTime"></param>
        public void UpdateScreens(GameTime gameTime)
        {
            for (int i = 0; i < Scenes.Count; i++ )
            {
                Scenes[i].Update(gameTime, ScreenManager.Input);
            }
        }

        /// <summary>
        /// Allows the screen to handle user input. Unlike Update, this method
        /// is only called when the screen is active, and not when some other
        /// screen has taken the focus.
        /// </summary>
        public virtual void HandleInput(InputManager input) { }

        #endregion

        #region Draw

        /// <summary>
        /// This is called when the screen should draw itself.
        /// </summary>
        public virtual void Draw(GameTime gameTime)
        {
            ViewportChanged = false;

            foreach (IScene scene in Scenes)
            {
                if (scene.Area != null)
                {
                    ScreenManager.GraphicsDevice.Viewport = new Viewport((Rectangle)scene.Area);
                    ViewportChanged = false;
                }
                else
                {
                    ScreenManager.GraphicsDevice.SetViewport(ViewportLocation.FullScreen);
                    ViewportChanged = true;
                }

                scene.Draw(gameTime, ScreenManager.SpriteBatch);
            }
        }

        #endregion

        #region Public Methods

        public void ExitScreen()
        {
            if (TransitionOffTime == TimeSpan.Zero)
            {
                // If the screen has a zero transition time, remove it immediately.
                ScreenManager.RemoveScreen(this);
            }
            else
            {
                // Otherwise flag that it should transition off and then exit.
                IsExiting = true;
            }
        }

        #endregion

        #endregion
    }
}
